package com.tool.app.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.StreamUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

@Slf4j
public class LoggingInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        traceRequest(request, body);
        long start = System.currentTimeMillis();
        ClientHttpResponse response = execution.execute(request, body);
        ClientHttpResponse responseWrapper = new BufferingClientHttpResponseWrapper(response);
        long end = System.currentTimeMillis();
        traceResponse(request, responseWrapper, (end - start) / 1000.0f);
        return responseWrapper;
    }

    private void traceRequest(HttpRequest request, byte[] body) {
        String bodyStr = new String(body, StandardCharsets.UTF_8);
        String logStr = "\n===========================request begin===========================\n" +
                "URI         : " + request.getURI() + "\n" +
                "Method      : " + request.getMethod() + "\n" +
                "Headers     : " + request.getHeaders() + "\n";
        if (StringUtils.isNotBlank(bodyStr)) {
            logStr += "Request body: " + bodyStr + "\n";
        }
        logStr += "===========================request end==============================";
        log.info(logStr);
    }

    private void traceResponse(HttpRequest request, ClientHttpResponse response, float time) throws IOException {
        String s = StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8);
        StringBuilder logStr = new StringBuilder();
        logStr.append("\n===========================response begin===========================\n")
              .append("TIME         : ").append(time).append("s").append("\n")
              .append("URI          : ").append(request.getURI()).append("\n")
              .append("Method       : ").append(request.getMethod()).append("\n")
              .append("Status code  : ").append(response.getStatusCode()).append("\n")
              .append("Headers      : ").append(response.getHeaders()).append("\n");
        if (StringUtils.isNotBlank(s)) {
            logStr.append("Response body: ").append(s).append("\n");
        }
        logStr.append("===========================response end==============================");
        log.info(logStr.toString());
    }

    // 自定义缓冲响应的包装类
    private static class BufferingClientHttpResponseWrapper implements ClientHttpResponse {
        private final ClientHttpResponse response;
        @Nullable
        private byte[] body;

        BufferingClientHttpResponseWrapper(ClientHttpResponse response) {
            this.response = response;
        }

        public HttpStatus getStatusCode() throws IOException {
            return this.response.getStatusCode();
        }

        public int getRawStatusCode() throws IOException {
            return this.response.getRawStatusCode();
        }

        public String getStatusText() throws IOException {
            return this.response.getStatusText();
        }

        public HttpHeaders getHeaders() {
            return this.response.getHeaders();
        }

        public InputStream getBody() throws IOException {
            if (this.body == null) {
                this.body = StreamUtils.copyToByteArray(this.response.getBody());
            }

            return new ByteArrayInputStream(this.body);
        }

        public void close() {
            this.response.close();
        }
    }
}
